package com.example.demo.person.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.read.listener.PageReadListener;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.person.dto.PersonImageImportDTO;
import com.example.demo.use_demo.a_excel_demo.*;
import com.example.demo.use_demo.a_excel_demo.listener.*;
import com.example.demo.use_demo.a_excel_demo.entity.ExcelImportResult;
import com.example.demo.use_demo.a_excel_demo.entity.ValidateCodeRuleEnum;
import com.example.demo.exception.BusinessException;
import com.example.demo.person.dto.PersonDTO;
import com.example.demo.person.enums.MaritalStatusEnum;
import com.example.demo.person.enums.PersonLevelEnum;
import com.example.demo.person.mapper.PersonMapper;
import com.example.demo.person.pojo.Person;
import com.example.demo.person.vo.PersonVO;
import com.example.demo.person.dto.AdvanceQueryPersonDTO;
import com.example.demo.common.request.SSPPageRequest;
import com.example.demo.use_demo.a_excel_demo.utils.ExcelPicReadUtil;
import com.github.pagehelper.PageInfo;
import com.github.pagehelper.page.PageMethod;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xssf.usermodel.XSSFPictureData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import redis.clients.jedis.Jedis;

import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import static com.example.demo.person.constants.PersonConstant.levelNameCodeMap;
import static com.example.demo.person.constants.PersonConstant.maritalStatusMap;

/**
 * @author zhangqiang
 * @date 2021/5/29 15:11
 */
@Slf4j
@Service
public class PersonServiceImpl extends ServiceImpl<PersonMapper, Person> implements IPersonService {

    @Autowired
    private PersonMapper personMapper;

    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;

    @Autowired
    private Jedis jedis;

    /**
     * 保存person
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void savePerson(PersonDTO personDTO) {
        Person person = BeanUtil.copyProperties(personDTO, Person.class);
        personMapper.insert(person);
    }

    /**
     * 更新person
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updatePerson(PersonDTO personDTO) {
        Person person = BeanUtil.copyProperties(personDTO, Person.class);
        personMapper.updateById(person);
    }

    /**
     * 列表查询person
     */
    @Override
    public PageInfo<PersonVO> listByAdvanceQuery(SSPPageRequest<AdvanceQueryPersonDTO> queryParam) {
        PageMethod.startPage(queryParam.getCurrentPage(), queryParam.getPageSize());

        List<Person> personList = personMapper.listByAdvanceQuery(queryParam.getParam());
        List<PersonVO> personVOList = BeanUtil.copyToList(personList, PersonVO.class);
        personVOList.forEach(item -> {
            item.setMaritalStatusName(MaritalStatusEnum.getDescByCode(item.getMaritalStatus()));
        });
        return PageInfo.of(personVOList);
    }

    /**
     * 查询
     */
    @Override
    public List<Person> tempQuery(){
        List<String> namesKey = Arrays.asList("海","海洋");
        return personMapper.selectList(Wrappers.<Person>lambdaQuery()
                .eq(Person::getLevel, PersonLevelEnum.Middle.code)
                .and(wrappers -> wrappers.gt(Person::getAge,18).lt(Person::getAge,30))
                .and(wrappers -> {
                    namesKey.forEach(nameKey -> {
                        wrappers.or(wrapper -> wrapper.like(Person::getName,nameKey).eq(Person::getJobNo,"63217"));
                    });
                }));
    }

    //===============================================Excel导入=START=====================================================
    // --------------------------------------------------EasyExcel
    /**
     * EasyExcel通用型XxxListener对象导入(适用大量数据场景)
     */
    public ExcelImportResult importDomainEasyExcel(InputStream inputStream) {
        ExcelDataConverter<Person> excelDataConverter = ExcelDataConverter.newConverter(Person.class);
        ExcelDataValidator<Person> excelDataValidator = ExcelDataValidator.newValidator(Person.class);
        // 数据转换
        excelDataConverter.wrapConvert(levelNameCodeMap, "level")
                .wrapConvertWithCustomConsumer(data -> {
                    String maritalStatusName = data.getMaritalStatusName();
                    data.setMaritalStatus(maritalStatusMap.get(maritalStatusName));
                })
                .wrapConvertWithCustomConsumer(data -> {
                    data.setCreateUserId(1L);
                    data.setCreateTime(DateTime.now());
                    data.setUpdateUserId(1L);
                    data.setUpdateTime(DateTime.now());
                });
        // 数据验证
        excelDataValidator.wrapRule(ValidateCodeRuleEnum.NOT_BLANK, "jobNo", "name", "age")
                .wrapRuleWithDataLength(8, "jobNo")
                .wrapRuleWithDataLengthLimit(1, 10, "name")
                .wrapRuleWithDataIntLimit(18, 60, "age")
                .wrapRule(ValidateCodeRuleEnum.DATE_YYYY_MM_DD, "birthday")
                .wrapRuleWithNoRepeat(true, "jobNo", "name", "age", "nationality")
                .wrapRuleWithCustomFunction(data -> {
                    String nationality = data.getNationality();
                    return !"台湾".equals(nationality);
                }, "请重新填写国籍！")
                .wrapRuleWithCustomFunction(data -> {
                    Integer maritalStatus = data.getMaritalStatus();
                    return maritalStatus != null;
                }, "请填写正确的婚姻状况！")
                .wrapRuleWithFieldKeyNameMap(new HashMap<String, String>(8) {{
                    put("jobNo", "工号");
                    put("name", "姓名");
                    put("age", "年龄");
                    put("birthday", "出生年月");
                    put("nationality", "国籍");
                    put("maritalStatusName", "婚姻状况");
                    put("level", "级别");
                }});
        // 数据导入方式一
        ExcelDomainListener<Person> excelDomainListener = new ExcelDomainListener<>(PersonServiceImpl.class, "saveData",excelDataConverter, excelDataValidator);
        // 数据导入方式二
        ExcelDomainListener2<Person> excelDomainListener2 = new ExcelDomainListener2<>(this::saveData,excelDataConverter,excelDataValidator);

        ExcelReader excelReader = EasyExcel.read(inputStream).registerReadListener(excelDomainListener2).head(Person.class).build();
        excelReader.readAll();
        return excelDomainListener2.excelImportResult;
    }

    /**
     * EasyExcel指定型PersonListener对象导入(适用大量数据场景)
     */
    public ExcelImportResult importPersonEasyExcel(InputStream inputStream) {
        ExcelDataConverter<Person> excelDataConverter = ExcelDataConverter.newConverter(Person.class);
        ExcelDataValidator<Person> excelDataValidator = ExcelDataValidator.newValidator(Person.class);
        // 数据转换
        excelDataConverter.wrapConvert(levelNameCodeMap, "level")
                .wrapConvertWithCustomConsumer(data -> {
                    String maritalStatusName = data.getMaritalStatusName();
                    data.setMaritalStatus(maritalStatusMap.get(maritalStatusName));
                })
                .wrapConvertWithCustomConsumer(data -> {
                    data.setCreateUserId(1L);
                    data.setCreateTime(DateTime.now());
                    data.setUpdateUserId(1L);
                    data.setUpdateTime(DateTime.now());
                });
        // 数据验证
        excelDataValidator.wrapRule(ValidateCodeRuleEnum.NOT_BLANK, "jobNo", "name", "age")
                .wrapRuleWithDataLength(8, "jobNo")
                .wrapRuleWithDataLengthLimit(1, 10, "name")
                .wrapRuleWithDataIntLimit(18, 60, "age")
                .wrapRule(ValidateCodeRuleEnum.DATE_YYYY_MM_DD, "birthday")
                .wrapRuleWithNoRepeat(true, "jobNo", "name", "age", "nationality")
                .wrapRuleWithCustomFunction(data -> {
                    String nationality = data.getNationality();
                    return !"台湾".equals(nationality);
                }, "请重新填写国籍！")
                .wrapRuleWithCustomFunction(data -> {
                    Integer maritalStatus = data.getMaritalStatus();
                    return maritalStatus != null;
                }, "请填写正确的婚姻状况！")
                .wrapRuleWithFieldKeyNameMap(new HashMap<String, String>(8) {{
                    put("jobNo", "工号");
                    put("name", "姓名");
                    put("age", "年龄");
                    put("birthday", "出生年月");
                    put("nationality", "国籍");
                    put("maritalStatusName", "婚姻状况");
                    put("level", "级别");
                }});
        // 数据导入方式一
        ExcelPersonListener excelPersonListener = new ExcelPersonListener(this, excelDataConverter, excelDataValidator);
        // 数据导入方式二
        ExcelPersonListener2 excelPersonListener2 = new ExcelPersonListener2(this::saveData, excelDataConverter, excelDataValidator);

        ExcelReader excelReader = EasyExcel.read(inputStream).registerReadListener(excelPersonListener2).head(Person.class).build();
        excelReader.readAll();
        return excelPersonListener2.excelImportResult;
    }

    /**
     * EasyExcel指定型Map对象导入(适用大量数据场景)
     */
    @SuppressWarnings("unchecked")
    public ExcelImportResult importMapEasyExcel(InputStream inputStream) {
        ExcelDataConverter<Map> excelDataConverter = ExcelDataConverter.newConverter(Map.class);
        ExcelDataValidator<Map> excelDataValidator = ExcelDataValidator.newValidator(Map.class);
        // 数据转换
        excelDataConverter.wrapConvert(levelNameCodeMap, "level")
                .wrapConvertWithCustomConsumer(data -> {
                    String maritalStatusName = String.valueOf(data.get("maritalStatus"));
                    data.put("maritalStatus", maritalStatusMap.get(maritalStatusName));
                })
                .wrapConvertWithCustomConsumer(data -> {
                    data.put("createUserId", 1L);
                    data.put("createTime", DateTime.now());
                    data.put("updateUserId", 1L);
                    data.put("updateTime", DateTime.now());
                });
        // 数据验证
        excelDataValidator.wrapRule(ValidateCodeRuleEnum.NOT_BLANK, "jobNo", "name", "age")
                .wrapRuleWithDataLength(8, "jobNo")
                .wrapRuleWithDataLengthLimit(1, 10, "name")
                .wrapRuleWithDataIntLimit(18, 60, "age")
                .wrapRule(ValidateCodeRuleEnum.DATE_YYYY_MM_DD, "birthday")
                .wrapRuleWithNoRepeat(true, "jobNo", "name", "age", "nationality")
                .wrapRuleWithCustomFunction(data -> {
                    String nationality = String.valueOf(data.get("nationality"));
                    return !"台湾".equals(nationality);
                }, "请重新填写国籍！")
                .wrapRuleWithCustomFunction(data -> {
                    Object maritalStatus = data.get("maritalStatus");
                    return maritalStatus != null;
                }, "请填写正确的婚姻状况！")
                .wrapRuleWithFieldKeyNameMap(new HashMap<String, String>(8) {{
                    put("jobNo", "工号");
                    put("name", "姓名");
                    put("age", "年龄");
                    put("birthday", "出生年月");
                    put("nationality", "国籍");
                    put("maritalStatus", "婚姻状况");
                    put("level", "级别");
                }});
        // 数据导入
        Map<Integer, String> columnIndexFieldKeyMap = new HashMap<Integer, String>(8) {{
            put(0, "jobNo");
            put(1, "name");
            put(2, "age");
            put(3, "birthday");
            put(4, "nationality");
            put(5, "maritalStatus");
            put(6, "level");
        }};
        ExcelMapListener excelMapListener = new ExcelMapListener(this::saveDataMap, columnIndexFieldKeyMap, excelDataConverter, excelDataValidator);
        ExcelReader excelReader = EasyExcel.read(inputStream).registerReadListener(excelMapListener).build();
        excelReader.readAll();
        return excelMapListener.excelImportResult;
    }

    /**
     * EasyExcel 注解方式简单导出
     */
    public void exportEasyExcel(HttpServletResponse response) throws Exception {
        response.setContentType("application/vnd.ms-excel; charset=utf-8");
        response.setHeader("Content-Disposition", "attachment; fileName=" + URLEncoder.encode("人员查询", "utf-8"));
        response.setCharacterEncoding("utf-8");

        List<Person> personList = personMapper.selectList(Wrappers.lambdaQuery());
        List<PersonVO> personVOList = BeanUtil.copyToList(personList, PersonVO.class);
        personVOList.forEach(item -> {
            item.setBirthdayStr(item.getBirthday() == null ? null : DateUtil.format(item.getBirthday(),DatePattern.NORM_DATE_FORMAT));
            item.setMaritalStatusName(MaritalStatusEnum.getDescByCode(item.getMaritalStatus()));
        });

        ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), PersonVO.class).build();
        WriteSheet writeSheet = EasyExcel.writerSheet(0).build();
        excelWriter.write(personVOList,writeSheet);
        excelWriter.finish();
        excelWriter.close();
    }

    // --------------------------------------------------HuTool
    /**
     * HuTool通用型对象导入(适用少量数据场景,大量数据场景需要使用sax读取方式)
     */
    public ExcelImportResult importDomainHuTool(InputStream inputStream) {
        // 表头
        Map<String, String> headerAliasMap = new HashMap<String, String>(8) {
            private static final long serialVersionUID = 1601663072410432060L;
            {
                put("工号", "jobNo");
                put("姓名", "name");
                put("年龄", "age");
                put("出生年月", "birthday");
                put("国籍", "nationality");
                put("婚姻状况", "maritalStatusName");
                put("级别", "level");
            }};
        // 必须存在的表头
        List<String> validateHeaders = Arrays.asList("工号", "姓名", "年龄", "婚姻状况");

        ExcelReaderExtHuTool<Person> readerExtension = ExcelUtilExtHuTool.getReaderExtension(inputStream, Person.class);
        readerExtension.setHeaderAlias(headerAliasMap);
        readerExtension.setValidateHeaders(validateHeaders);
        // 数据转换
        readerExtension.dataValueConverter().wrapConvert(levelNameCodeMap, "level")
                .wrapConvertWithCustomConsumer(data -> {
                    String maritalStatusName = data.getMaritalStatusName();
                    data.setMaritalStatus(maritalStatusMap.get(maritalStatusName));
                })
                .wrapConvertWithCustomConsumer(data -> {
                    data.setCreateUserId(1L);
                    data.setCreateTime(DateTime.now());
                    data.setUpdateUserId(1L);
                    data.setUpdateTime(DateTime.now());
                });
        // 数据验证
        readerExtension.businessRuleValidator().wrapRule(ValidateCodeRuleEnum.NOT_BLANK, "jobNo", "name", "age")
                .wrapRuleWithDataLength(8, "jobNo")
                .wrapRuleWithDataLengthLimit(1, 10, "name")
                .wrapRuleWithDataIntLimit(18, 60, "age")
                .wrapRule(ValidateCodeRuleEnum.DATE_YYYY_MM_DD, "birthday")
                .wrapRuleWithNoRepeat(true, "jobNo", "name", "age", "nationality")
                .wrapRuleWithCustomFunction(data -> {
                    String nationality = data.getNationality();
                    return !"台湾".equals(nationality);
                }, "请重新填写国籍！")
                .wrapRuleWithCustomFunction(data -> {
                    Integer maritalStatus = data.getMaritalStatus();
                    return maritalStatus != null;
                }, "请填写正确的婚姻状况！");
        // 数据导入
        List<Person> personList = readerExtension.readExcel(0, 1);
        ExcelImportResult excelImportResult = new ExcelImportResult();
        excelImportResult.setAddSuccessCount(personList.size());
        excelImportResult.setErrorMsgList(readerExtension.getAllErrors());
        super.saveBatch(personList);
        return excelImportResult;
    }

    /**
     * HuTool 循环查询导出
     */
    public void exportLoopWriteHuTool(HttpServletResponse response) {
        // 表头
        List<String> headers = new ArrayList<String>() {{
            add("工号"); add("姓名"); add("年龄");
            add("出生年月"); add("国籍"); add("婚姻状况"); add("级别");
        }};
        // 数据映射
        Map<String, String> headerAliasMap = new HashMap<String, String>() {{
            put("jobNo", "工号"); put("name", "姓名");
            put("age", "年龄"); put("birthdayStr", "出生年月");
            put("nationality", "国籍"); put("maritalStatusName", "婚姻状况");
            put("level", "级别");
        }};

        ExcelExportUtilExtHuTool.exportLoopWriteHt(response, headers, headerAliasMap, currentPage -> {
            Page<Person> personPage = new Page<>(currentPage, 2);
            List<Person> records = personMapper.selectPage(personPage, Wrappers.lambdaQuery()).getRecords();
            List<PersonVO> personVOList = BeanUtil.copyToList(records, PersonVO.class);
            personVOList.forEach(item -> {
                item.setBirthdayStr(item.getBirthday() == null ? null : DateUtil.format(item.getBirthday(), DatePattern.NORM_DATE_FORMAT));
                item.setMaritalStatusName(MaritalStatusEnum.getDescByCode(item.getMaritalStatus()));
            });
            return personVOList;
        }, "人员查询");
    }

    /**
     * HuTool 循环查询导出
     */
    public void exportLoopWriteEasyExcel(HttpServletResponse response) {
        ExcelExportUtilExtHuTool.exportLoopWriteEe(response, PersonVO.class, currentPage -> {
            log.info("当前查询页{}", currentPage);
            Page<Person> personPage = new Page<>(currentPage, 2);
            List<Person> personList = personMapper.selectPage(personPage, Wrappers.lambdaQuery()).getRecords();
            List<PersonVO> personVOList = BeanUtil.copyToList(personList, PersonVO.class);
            personVOList.forEach(item -> {
                item.setBirthdayStr(item.getBirthday() == null ? null : DateUtil.format(item.getBirthday(),DatePattern.NORM_DATE_FORMAT));
                item.setMaritalStatusName(MaritalStatusEnum.getDescByCode(item.getMaritalStatus()));
            });
            return personVOList;
        }, "人员查询");
    }

    // 批次事务
    public void saveData(List<Person> personList) {
        super.saveBatch(personList);
        log.info("数据保存..." + personList.toString());
        if (false) {
            // TODO 数据验证 (数据库验证(适用大数量批次唯一验证) 或者 其他)
            throw new BusinessException("数据保存失败");
        }
    }

    // 批次事务
    public void saveDataMap(List<Map<String,String>> dataList) {
        List<Person> personList = BeanUtil.copyToList(dataList, Person.class);
        super.saveBatch(personList);
        log.info("数据保存..." + personList.toString());
        if (false) {
            // TODO 数据验证 (数据库验证(适用大数量批次唯一验证) 或者 其他)
            throw new BusinessException("数据保存失败");
        }
    }

    // --------------------------------------------------Excel图片读取
    /**
     * 导入图片
     * @param inputStream 文件流
     * @return 导入结果
     */
    public String importPersonHeadImage(InputStream inputStream) {
        byte[] excelFileBytes = ExcelPicReadUtil.getFileStream(inputStream);
        // 读取基本信息
        List<PersonImageImportDTO> personImageImports = new ArrayList<>();
        ExcelReader excelReader = EasyExcel.read(new ByteArrayInputStream(excelFileBytes)).registerReadListener(new PageReadListener<PersonImageImportDTO>(personImageImports::addAll)).head(PersonImageImportDTO.class).build();
        excelReader.readAll();
        if(CollectionUtil.isEmpty(personImageImports)){
            return "无数据";
        }

        // 读取并保存图片
        Map<String, String> picIdNameMap = ExcelPicReadUtil.readAndSaveImage(excelFileBytes);
        // 对象构建
        personImageImports.forEach(i -> {
            String headImageExcelIdStr = i.getHeadImageExcelId();
            String headImageExcelId = headImageExcelIdStr.substring(headImageExcelIdStr.indexOf("\"") + 1, headImageExcelIdStr.lastIndexOf("\""));
            i.setHeadImageName(picIdNameMap.get(headImageExcelId));
        });
        return "导入成功" + personImageImports.size() + "条";
    }

    /**
     * 下载图片
     * @param fileName 图片名称
     * @param response 响应流
     */
    public void downloadPersonHeadImage(String fileName, HttpServletResponse response) throws Exception {
        ExcelPicReadUtil.downloadImage(fileName,response);
    }
    //================================================Excel导入=END=====================================================

    //===========================================异步多线程FutureTask=START=============================================
    /**
     * 异步多线程 有返回值
     */
    public List<PersonVO> getPersonsByLevels(List<String> levels){
        List<CompletableFuture<List<PersonVO>>> futures = new ArrayList<>();
        levels.forEach(level -> {
            CompletableFuture<List<PersonVO>> future = CompletableFuture.supplyAsync(() -> {
                List<Person> personList = personMapper.selectList(Wrappers.<Person>lambdaQuery().eq(Person::getLevel, level));
                return BeanUtil.copyToList(personList, PersonVO.class);
            },taskExecutor);
            futures.add(future);
        });
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
        return allFutures.thenApply(a -> futures.stream().map(CompletableFuture::join).flatMap(List::stream).collect(Collectors.toList())).join();
    }

    /**
     * 异步多线程 无返回值
     */
    public void updatePersonsByLevels(List<String> levels){
        List<CompletableFuture<Void>> futures = new ArrayList<>();
        levels.forEach(level -> {
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                personMapper.update(new Person(), Wrappers.<Person>lambdaUpdate().set(Person::getNationality, "中国").eq(Person::getLevel, level));
            }, taskExecutor);
            futures.add(future);
        });
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
    }
    //=============================================异步多线程FutureTask=END=============================================

    //=====================================================Redis Start==================================================
    /**
     * ZSet add
     */
    @Override
    public void setPersonScore(Long pId, Long score) {
        jedis.zadd("ranking", score, String.valueOf(pId));
    }

    /**
     * ZSet remove
     */
    @Override
    public void remPersonScore(Long pId) {
        jedis.zrem("ranking",String.valueOf(pId));
    }

    /**
     * 获取 ZSet
     */
    @Override
    public Set<String> getPersonRanking() {
        return jedis.zrevrangeByScore("ranking", 100, 0);
    }

    /**
     * 点赞
     * jedis.zincrby
     * jedis.zrevrange
     * jedis.zrevrank
     * jedis.zscore
     */
    @Override
    public void like(Long articleId) {
        jedis.incr("article:" + String.valueOf(articleId));
    }

    /**
     * 取消点赞
     */
    @Override
    public void cancelLike(Long articleId) {
        jedis.decr("article:" + String.valueOf(articleId));
    }

    /**
     * 获取点赞数量
     */
    @Override
    public Long getLikedCount(Long articleId) {
        return Long.valueOf(jedis.get("article:" + String.valueOf(articleId)));
    }

    //---------------------------------------------------Redis END------------------------------------------------------

}

